﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using CoreEx.Common.Implementation.Linq;
using CoreEx.Database.Metadata;
using LinFu.IoC.Interfaces;
using LinFu.IoC;
using LinFu.Reflection;
using CoreEx.Common.Linq;

namespace CoreEx.Database.Query.Linq.Implementation
{
    public class ExpressionProjector : ExpressionVisitor, IExpressionProjector, IInitialize<IServiceRequestResult>
    {
        private IExpressionFactory _expressionFactory;
        private ITypeMappingRepository _mappingRepository;
        
        private IAliasMap _aliasMap;
        private Type rootType;
        private IServiceContainer _serviceContainer;
        private SelectQueryExpression _selectQueryExpression;
        private readonly Stack<MemberExpression> _memberExpressions = new Stack<MemberExpression>();

        public SelectQueryExpression CreateProjection(Expression expression)
        {
            rootType = ((LambdaExpression) expression).Parameters[0].Type;
            _selectQueryExpression = CreateProjection(rootType);
            Visit(expression);
            CreateJoinExpression();
            return _selectQueryExpression;
            
        }

        private SelectQueryExpression CreateProjection(Type type)
        {
            var query = _serviceContainer.GetService<IQuery>();
            var tableMappingInfo = _mappingRepository[type];
            var alias = _aliasMap.GetNextAlias(tableMappingInfo.TableInfo.TableName);
            var projection = tableMappingInfo.ColumnMappings.Values.Where(cm => cm.ColumnInfo != null).Select(
                cm => _expressionFactory.Column(alias, cm.ColumnInfo.ColumnName)).ToList();
            return query.Select(projection.ToArray()).From(e => e.Alias(e.Table(tableMappingInfo.TableInfo.TableName), alias));
        }


        protected override Expression VisitMemberAccess(MemberExpression m)
        {
            if (typeof(MemberExpression).IsAssignableFrom(m.Expression.GetType()))            
                _memberExpressions.Push((MemberExpression)m.Expression);
            
         
              
          
            return base.VisitMemberAccess(m);
        }


        

        private void CreateJoinExpression()
        {
            if (_memberExpressions.Count() == 0)
                return;                       

            var memberExpression = _memberExpressions.Pop();

            var propertyInfo = (PropertyInfo)memberExpression.Member;

            var foreignTableMappingInfo = _mappingRepository[memberExpression.Member.DeclaringType];
            var primaryTableMappingInfo = _mappingRepository[memberExpression.Type];

            var foreignTableAlias = _aliasMap.GetCurrentAlias(foreignTableMappingInfo.TableInfo.TableName);
            var primaryTableAlias = _aliasMap.GetCurrentAlias(primaryTableMappingInfo.TableInfo.TableName);
            var foreignKey = foreignTableMappingInfo.ColumnMappings[propertyInfo].ForeignKeyInfo;


            IExpression expression = null;
            foreach (var foreignKeyColumn in foreignKey.Columns)
            {
                var conditionExpression = _expressionFactory.Column
                        (foreignTableAlias, foreignKeyColumn.ForeignColumnInfo.ColumnName) ==
                        _expressionFactory.Column(primaryTableAlias, foreignKeyColumn.PrimaryColumnInfo.ColumnName);
                if (expression == null)
                    expression = conditionExpression;
                else
                    expression = (ExpressionBase)expression & conditionExpression;
            }
            _selectQueryExpression.InnerJoin(
                _expressionFactory.Alias(e => e.Table(primaryTableMappingInfo.TableInfo.TableName), primaryTableAlias), expression);

            CreateJoinExpression();
        }


       


        public void Initialize(IServiceRequestResult source)
        {
            _expressionFactory = source.Container.GetService<IExpressionFactory>();
          
            _mappingRepository = source.Container.GetService<ITypeMappingRepository>(source.ServiceName);
            _aliasMap = source.Container.GetService<IAliasMap>();
            _serviceContainer = source.Container;
        }
    }
}
